open IntInf;

fun read file =
    let
        val inStream = TextIO.openIn file
    in
        (* TextIO.inputAll returns a TextIO.vector, which is a string. *)
        TextIO.inputAll inStream
    end;

val newline_tokenizer = String.tokens (fn c => c = #"\n");
val space_tokenizer = String.tokens (fn c => c = #" ");

datatype HorizontalChange = HorizontalChange of IntInf.int;
datatype DepthChange = DepthChange of IntInf.int;
datatype AimChange = AimChange of IntInf.int;
type change = HorizontalChange * DepthChange * AimChange;
type movement = string * IntInf.int;


fun parse_line line =
    let
        val (dir::len_as_str::_) = space_tokenizer line;
        val len = case IntInf.fromString len_as_str of
                      SOME number => number
                    | NONE => raise Fail "encountered a non-int string";
        val move = (dir, len): movement;
    in
        move
    end;

fun update_change (move as (dir, len), prev_change) =
    let
        val (HorizontalChange prev_horizontal, DepthChange prev_depth, AimChange prev_aim) = prev_change;
    in
        case dir of
            "forward" =>
            (HorizontalChange (prev_horizontal + len),
             DepthChange (prev_depth + (prev_aim * len)),
             AimChange prev_aim)
          | "up" => (HorizontalChange prev_horizontal,
                     DepthChange prev_depth,
                     AimChange (prev_aim - len))
          | "down" => (HorizontalChange prev_horizontal,
                       DepthChange prev_depth,
                       AimChange (prev_aim + len))
          | _ => raise Fail "unrecognized direction"
    end;

val initial_change = (HorizontalChange 0, DepthChange 0, AimChange 0): change;
val str = read "input";
val lines = newline_tokenizer str;

let
    val (HorizontalChange h, DepthChange depth, AimChange aim) =
        foldl (fn (line, (acc: change)) =>
                  update_change ((parse_line line), acc))
              initial_change
              lines;
    val solution = h * depth;
in
    solution
end;
